/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.domain;


import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import org.vacoor.nothing.common.util.Castor;

import java.lang.reflect.Array;
import java.util.*;

/**
 * 搜索请求
 * <p/>
 * 一个 {@link Filters} 对象是一组 {@link org.vacoor.nothing.web.domain.Filters.Filter} 的集合
 * Keyword 之间为 且 的关系 (即 AND 关系)
 * 每个 Keyword 定义了对一个 Field 的限制
 * 一个 Keyword 中的 Field 可以有多个值, 它们是 或 的关系 (即 OR )
 * <p/>
 *
 * @author vacoor
 */
public class Filters implements Iterable<Filters.Filter> {
    private List<Filter> filters = new ArrayList<Filter>();

    /**
     * 创建一个过滤器
     */
    public static Filters create() {
        return new Filters();
    }

    /**
     * 构造一个过滤器
     */
    public Filters(Filter... filters) {
        for (Filter filter : filters) {
            add(filter);
        }
    }

    /**
     * 基于给定拷贝创建一个过滤器
     */
    public Filters(Filters filters) {
        if (filters != null) {
            for (Filter filter : filters) {
                add(filter);
            }
        }
    }

    /**
     * 添加一个 ==
     */
    public Filters eq(String property, Object... values) {
        return add(Filter.eq(property, values));
    }

    public Filters ne(final String property, final Object... values) {
        return add(Filter.ne(property, values));
    }

    public Filters gt(final String property, final Object... values) {
        return add(Filter.gt(property, values));
    }

    public Filters lt(final String property, final Object... values) {
        return add(Filter.lt(property, values));
    }

    public Filters ge(final String property, final Object... values) {
        return add(Filter.ge(property, values));
    }

    public Filters le(final String property, final Object... values) {
        return add(Filter.le(property, values));
    }

    public Filters like(final String property, final Object... values) {
        return add(Filter.like(property, values));
    }

    /* *******************
     *  update 2015.01.17
     * ***************** */

    public Filters isNull(final String property) {
        return add(Filter.isNull(property));
    }

    public Filters isNotNull(final String property) {
        return add(Filter.isNotNull(property));
    }

    public Filters beginsWith(final String property, Object... values) {
        return add(Filter.beginsWith(property, values));
    }

    public Filters notBeginsWith(final String property, Object... values) {
        return add(Filter.notBeginsWith(property, values));
    }

    public Filters endsWith(final String property, Object... values) {
        return add(Filter.endsWith(property, values));
    }

    public Filters notEndsWith(final String property, Object... values) {
        return add(Filter.notEndsWith(property, values));
    }

    public Filters contains(final String property, final Object... values) {
        return add(Filter.contains(property, values));
    }

    public Filters notContains(final String property, final Object... values) {
        return add(Filter.notContains(property, values));
    }

    public Filters add(Filter filter) {
        filters.add(filter);
        return this;
    }

    public Filters add(String property, Operator op, Object... values) {
        return add(new Filter(property, op, values));
    }

    public Filters and(Filters filters) {
        if (filters == null) {
            return this;
        }

        Filters these = new Filters(this);
        for (Filter filter : filters) {
            these.add(filter);
        }
        return these;
    }

    public void clear() {
        filters.clear();
    }

    public int size() {
        return filters.size();
    }

    public Filter getFilterFor(String property) {
        for (Filter filter : filters) {
            if (filter.getProperty().equals(property)) {
                return filter;
            }
        }
        return null;
    }

    @Override
    public Iterator<Filter> iterator() {
        return filters.iterator();
    }


    /* **********************************
     *              操作符
     * ******************************** */
    public enum Operator {
        EQ("="), NE("<>"), GT(">"), LT("<"), GE(">="), LE("<="),
        LK("LIKE") {
            @Override
            public Object wrapValueIfNecessary(Object value) {
                return CN.wrapValueIfNecessary(value);
            }
        },
        // 参考 jqGrid 新增 update 2015.01.17
        NU("IS NULL") {
            @Override
            public Object wrapValueIfNecessary(Object value) {
                return null;
            }
        },
        NN("IS NOT NULL") {
            @Override
            public Object wrapValueIfNecessary(Object value) {
                return null;
            }
        },
        BW("LIKE") { // begins with

            @Override
            public Object wrapValueIfNecessary(Object value) {
                String s = Castor.asString(value);
                return null != s && !s.endsWith("%") ? s + "%" : s;
            }
        },
        BN("NOT LIKE") { // not begins with

            @Override
            public Object wrapValueIfNecessary(Object value) {
                return BW.wrapValueIfNecessary(value);
            }
        },
        EW("LIKE") { // ends with

            @Override
            public Object wrapValueIfNecessary(Object value) {
                String s = Castor.asString(value);
                return null != s && !s.startsWith("%") ? "%" + s : s;
            }
        },
        EN("NOT LIKE") { // not ends with

            @Override
            public Object wrapValueIfNecessary(Object value) {
                return EW.wrapValueIfNecessary(value);
            }
        },
        CN("LIKE") { // contains

            @Override
            public Object wrapValueIfNecessary(Object value) {
                return BW.wrapValueIfNecessary(EW.wrapValueIfNecessary(value));
            }
        },
        NC("NOT LIKE") { // not contains

            @Override
            public Object wrapValueIfNecessary(Object value) {
                return CN.wrapValueIfNecessary(value);
            }
        };

        private final String op;

        Operator(String op) {
            this.op = op;
        }

        public Object wrapValueIfNecessary(Object value) {
            return value;
        }

        public static Operator forName(String name) {
            try {
                return valueOf(name.toUpperCase());
            } catch (Exception e) {
                return null;
            }
        }

        @Override
        public String toString() {
            return op;
        }
    }

    /**
     * 一个用于数据过滤的过滤器
     * <p/>
     * example:
     * Filter.eq('name', 'admin')
     * 当一个属性有多个值时, 应该解析为 OR
     * Filter.eq('name', 'admin', 'root') --> name = 'admin' OR name = 'root'
     *
     * @author vacoor
     */

    public static class Filter implements Iterable<Object> {
        private String property;
        private Operator operator;
        private Object values;

        public Filter(String property, Operator operator, Object values) {
            if (null == operator) {
                throw new IllegalArgumentException("operator must be not null");
            }
            this.property = property;
            this.operator = operator;
            this.values = values;
        }

        public String getProperty() {
            return property;
        }

        public Operator getOperator() {
            return operator;
        }

        public Object getValues() {
            return values;
        }

        /*
         * update 2015.01.17 增加 wrapValueIfNecessary 处理
         */
        @Override
        @SuppressWarnings({"rawtypes", "unchecked"})
        public Iterator<Object> iterator() {
            if (isNullOp(operator)) {
                return Iterators.forArray(new Object[]{null});
            }
            final Object values = this.values != null ? this.values : new Object[0];
            final Function<Object, Object> func = new Function<Object, Object>() {
                @Override
                public Object apply(Object input) {
                    return operator.wrapValueIfNecessary(input);
                }
            };

            // 集合
            if (values instanceof Collection) {
                // return ((Collection<?>) values).iterator();
                return Iterators.transform(((Collection<?>) values).iterator(), func);
            }

            // 枚举
            if (values instanceof Enumeration<?>) {
                // return Iterators.forEnumeration((Enumeration<?>) values);
                return Iterators.transform(Iterators.forEnumeration((Enumeration<?>) values), func);
            }

            Class<?> clazz = values.getClass();
            // 数组
            if (clazz.isArray()) {
                return new UnmodifiableIterator<Object>() {
                    private int index = 0;
                    private int len = Array.getLength(values);

                    @Override
                    public boolean hasNext() {
                        return index < len;
                    }

                    @Override
                    public Object next() {
                        // return Array.get(values, index++);
                        return func.apply(Array.get(values, index++));
                    }
                };
            }

            // pojo
            // return Iterators.forArray(values);
            return Iterators.forArray(func.apply(values));
        }

        private boolean isNullOp(Operator op) {
            return Operator.NU == op || Operator.NN == op;
        }

        @Override
        public String toString() {
            return "Filter: " + property + " " + operator + " " + Iterators.toString(iterator());
        }

        /* ******************
         *
         * ******************/

        public static Filter eq(final String property, final Object... values) {
            return new Filter(property, Operator.EQ, values);
        }

        public static Filter ne(final String property, final Object... values) {
            return new Filter(property, Operator.NE, values);
        }

        public static Filter gt(final String property, final Object... values) {
            return new Filter(property, Operator.GT, values);
        }

        public static Filter lt(final String property, final Object... values) {
            return new Filter(property, Operator.LT, values);
        }

        public static Filter ge(final String property, final Object... values) {
            return new Filter(property, Operator.GE, values);
        }

        public static Filter le(final String property, final Object... values) {
            return new Filter(property, Operator.LE, values);
        }

        public static Filter like(final String property, final Object... values) {
            return new Filter(property, Operator.LK, values);
        }

        /* *******************
         *  update 2015.01.17
         * ***************** */

        public static Filter isNull(final String property) {
            return new Filter(property, Operator.NU, null);
        }

        public static Filter isNotNull(final String property) {
            return new Filter(property, Operator.NN, null);
        }

        public static Filter beginsWith(final String property, Object... values) {
            return new Filter(property, Operator.BW, values);
        }

        public static Filter notBeginsWith(final String property, Object... values) {
            return new Filter(property, Operator.BN, values);
        }

        public static Filter endsWith(final String property, Object... values) {
            return new Filter(property, Operator.EW, values);
        }

        public static Filter notEndsWith(final String property, Object... values) {
            return new Filter(property, Operator.EN, values);
        }

        public static Filter contains(final String property, final Object... values) {
            return new Filter(property, Operator.CN, values);
        }

        public static Filter notContains(final String property, final Object... values) {
            return new Filter(property, Operator.NC, values);
        }
    }
}
